/*******************************************************************************
  Contiene sia la base di conoscenza degli agenti possibili (i pesi dei
  vari tipi di terreno, la geometria (con la consult(mappe) che
  include anche geometry e l'io_map) e il caricamento della mappa del
  livello con load_map
  */
:- use_module('types/syntax').
:- no_check(findall(_,_,_)).
check_me :- ensure_loaded('types/chk'),
	load([geometry, io_map, mappe, agenti]).
/******************************************************************************/

%---------------------------------------------------
%--------------------- KB TIPI ---------------------
%---------------------------------------------------
type punto := p(integer,integer).
type stati := stato(punto, direzione, list(integer), list(punto), list(punto), integer).
type sym_char := s; p; w; t. %%safe,pozzo,wumpus,tesoro.
type cella := cella(sym_char,punto).
type fn := m ; 'm.pl'.  %file names
type direzione := dir(number,number); nodir.
type list(T) := []; [T|list(T)].

%Deve essere caricata la mappa
load_map(Livello) :-
	(cella(cadavere,_)->retract(cella(cadavere,_));true),
	read_map(Livello, 'm.pl'),
	consult(m),
	show_map,!. %evita inutili tentativi di backtraking

%movimento(+P1,?P2,?Dir)	 nondet: P2 e' adiacente a P1
%       							 con direzione Dir
movimento(P1,P2,Dir):-
	rosa_venti(Dir), %controllo correttezza direzione
	spostamento(P1,P2,Dir), %controllo se esiste uno spostamento che unisca i punti tramite dir
	is_alive(P2),
	ok(P2).

% is_alive(+P1) det: l'agente è vivo in P1
% se la cella è safe oppure contiene un tesoro, oppure c'è un wumpus morto
% controllo se nel path è presente uno stato in cui si senta l'ululato
is_alive(P):-
	cella(safe,P);
	cella(tesoro,P);
	cella(cadavere,P).
	
%-----------------------------------------------------------------
%-------------------------- START e GOAL -------------------------
%-----------------------------------------------------------------
%START: start(+P,-S)	S e' il nodo di partenza
%						con posizione P deve prendere tutti i tesori
start(P0,stato(P0,dir(1,0),PList,[],Tesori,1)) :-
	make_perc(P0,PList),
    findall(P, cella(tesoro,P), Tesori).
%GOAL: i nodi goal: ha preso tutti i tesori
trovato(stato(p(1,1),_Dir,_Perc,_Zaino,[],_Arma)).
%-----------------------------------------------------------------

%-----------------------------------------------------------------
%------------------ COSTRUISCO LISTA PERCEZIONI ------------------
%-----------------------------------------------------------------
%guarda(+P,+D,-PERC) det: PERC è la percezione che l'agente
%						  ha nella cella p(X,Y) guardando
%						  nella direzione dir(DX,DY)
guarda(P,D,PERC):-
	sum(P,D,P2),
	ok(P2)->
		(cella(Z,P2),
		perc(Z,PERC));
		(Z = muro,
		perc(Z,PERC)).

%check(p(+X,+Y),[-N,-S,-W,-E]) det: controlla i primi vicini dip(X,Y)
%									costruisce la lista NSWE utilizzata poi per 
%									creare la lista percezioni
check(p(X,Y),[N,S,W,E]):-
	guarda(p(X,Y),dir(1,0),N),
	guarda(p(X,Y),dir(-1,0),S),
	guarda(p(X,Y),dir(0,-1),W),
	guarda(p(X,Y),dir(0,1),E).
		
%make_perc(+P,-Plist) det: crea la lista delle percezioni dell'agente nel punto P
make_perc(P,Plist):-
%	getPath(Path),
%	(not(member(stato(_,_,_,_,_,0),Path)),cella(cadavere,PC) -> 
%					(
%					retract(cella(cadavere,PC)),
%					assert(cella(wumpus,PC))
%%					writeln('mai sparato ma ho cadavere in':PC)
%					);true
%	),
	check(P,L),
	length(Plist,5),
	(member(f,L)->nth0(0,Plist,1);nth0(0,Plist,0)),
	(member(b,L)->nth0(1,Plist,1);nth0(1,Plist,0)),
	(member(s,L)->nth0(2,Plist,1);nth0(2,Plist,0)),
	(member(u,L)->nth0(3,Plist,1);nth0(3,Plist,0)),!,
	(cella(cadavere,_) -> nth0(4,Plist,1);nth0(4,Plist,0)).

%--------------------------------------------------------------
%--------------------- CALCOLO DEI VICINI --------------------- 
%--------------------------------------------------------------
vicini(S,L) :-	
	findall(X,vicino(S,X),L).
	
%MOVIMENTO
%vicino(stato(+P1,+D,+PL1,+Z,+T,+A),stato(-P2,+D,-PL2,+Z,+T,+A))
%semidet: P2 è la cella vicina a P1 tramite spostamento
%		  in direzione D. La lista delle perc in P2 è LP2
vicino(stato(P1,Dir,_,Zaino,Tesori,Arma),stato(P2,Dir,Perc2,Zaino,Tesori,Arma)):-
	movimento(P1,P2,Dir), %OCCHIO A NON METTERE TAGLI QUI!!!
	make_perc(P2,Perc2).
%MOVIMENTO+ORO
%vicino(stato(+P1,+D,+PL1,+Z1,+T1,+A),stato(-P2,+D,-PL2,-Z2,-T2,+A))
%semidet: P2 è la cella vicina a P1 tramite spostamento in direzione D.
%		  La lista delle percezioni in P2 è LP2 e zaino (Z) e tesori (T)
%		  vengono aggiornati spostando P2 da T a Z.
%E se nella cella di partenza si avverte uno scintillio
%e la cella d'arrivo è presente nella lista dei tesori. A questo punto la lista dei tesori
%verrà aggiornata togliendo tale cella e lo zaino verrà aggiornato aggiungendola
vicino(stato(P1,Dir,Perc1,Zaino1,Tesori1,Arma),stato(P2,Dir,Perc2,Zaino2,Tesori2,Arma)):-
	nth0(2,Perc1,1), %scintillio
	member(P2,Tesori1), %arrivo in una cella con tesoro
	movimento(P1,P2,Dir),!, %spostamento
	make_perc(P2,Perc2), %ricostruisco la lista percezioni
	subtract(Tesori1,[P2],Tesori2), %tolgo la cella d'arrivo dalla lista tesori
	append(Zaino1,[P2],Zaino2). %aggiungo la cella d'arrivo alla lista zaino
	
%ROTAZIONE
%vicino(stato(+P,dir(+X1,+Y1),+PL,+Z,+T,+A),stato(+P,dir(-X2,-Y2),+PL,+Z,+T,+A))
%semidet: dir(X2,Y2) è la direzione (perpendicolare) a quella iniziale (dir(X1,Y1))
%		  dell'agente che si trova nella cella P con lista di percezioni PL zaino Z
%		  tesori ancora da prendere T e arma A
vicino(stato(P,dir(X1,Y1),Perc,Z,T,A),stato(P,dir(X2,Y2),Perc,Z,T,A)):-
	scal([X1,Y1],[X2,Y2],0). %perpendicolarità
		
%UCCISIONE WUMPUS
%Magnanimo: uccido solo de non ho altre possibilità
%QUESTO È IL VECCHIO PREDICATO
%vicino(stato(P,D,[1,B,S,U,0],Z,T,1),stato(P,D,[1,B,S,U,1],Z,T,0)).
%vicino(stato(+P,+D,+LP1,+Z,+T,1),stato(+P,+D,-LP2,+Z,+T,0))
%det: LP2 è la lista delle percezioni nella cella P dopo che ho sparato
%	  (CON SUCCESSO) al wumpus dalla cella P in direzione D.
%	  Zaino (Z) e Tesoro (T) rimangono invariati, mentre l'arma passa da 1 a 0
vicino(stato(P,D,[1,_,_,_,0],Z,T,1),stato(P,D,LP2,Z,T,0)):-
	length(LP2,5),
	sum(P,D,P1),
%	(cella(cadavere,P1)->(retract(cella(cadavere,P1)),assert(cella(wumpus,P1)));true),
	cella(wumpus,P1),
	retract(cella(wumpus,P1)),
	assert(cella(cadavere,P1)),
	make_perc(P,LP2).
%vicino(stato(+P,+D,+LP1,+Z,+T,1),stato(+P,+D,-LP2,+Z,+T,0))
%det: LP2 è la lista delle percezioni nella cella P dopo che ho sparato
%	  (SENZA SUCCESSO) al wumpus dalla cella P in direzione D.
%	  Zaino (Z) e Tesoro (T) rimangono invariati, mentre l'arma passa da 1 a 0
vicino(stato(P,D,[1,_,_,_,0],Z,T,1),stato(P,D,LP,Z,T,0)):-
	sum(P,D,P1),
	not(cella(wumpus,P1)),
%	(cella(cadavere,Pc)->(writeln(Pc),retract(cella(cadavere,Pc)),assert(cella(wumpus,Pc)));true),
	make_perc(P,LP).
%	cella(Stat,p(3,1)),
%	writeln(Stat:LP:Arma2).

is_same(p(X1,Y1),p(X2,Y2)):-
	X1=:=X2,
	Y1=:=Y2 .	
%-----------------------------------------------------------
%-------------------------- COSTI --------------------------
%-----------------------------------------------------------
%FRECCIA COSTO:10
costo(stato(P,D,[1,_,_,_,0],Z,T,1),stato(P,D,[1|_],Z,T,0),10).
%MOVIMENTO
%costo(stato(+P1,+D,_,_,_,_),stato(_,+D,_,_,_,_),-Costo)
%semidet: Costo è il costo del movimento da P1 alla cella vicina in direzione D
costo(stato(P,D,_,_,_,_),stato(_,D,_,_,_,_),Costo):-
	prob(P,Costo).
%ROTAZIONE COSTO:0
%costo(stato(+P,dir(+X1,+Y1),_,_,_,_),stato(+P,dir(+X2,+Y2),_,_,_,_),0)
%det: il costo della rotazione nella cella P da una direzione dir(X1,Y1) ad una
%	  direzione dir(X2,Y2) è 0 (favorendo così l'esplorazione)
costo(stato(P,dir(X1,Y1),_,_,_,_),stato(P,dir(X2,Y2),_,_,_,_),0):-
	scal([X1,Y1],[X2,Y2],0).
%----------------------------------------------
%------------ RISCHI E PROBABILITÀ ------------
%----------------------------------------------
%RISCHIO
%risk(-R) det: R è il rischio che si corre in una cella spostandosi
%			   in una qualunque delle direzioni libere
%dipende dal numero totale di pozzi, tesori e wumpus, dalle celle visitate
%(dedotte dal Path) e dal numero di celle totali.
risk(R):-
	num(pozzi,P),
	num(tesori,T),
	num(wumpus,W),
	getPath(Path),
	lunghezza_path(Path,[],V),
	dim(A,B),
	Tot is A*B,
	R is P/(Tot-(V+T+W)).

%DIREZIONI DISPONIBILI
%direzioni_disponibili(p(+X,+Y),-R) det: R è il numero di direzioni che non ho 
%										 ancora intrapreso da una cella, ricavato
%										 dall'intersezione della lista dei primi vicini
%										 e del Path
direzioni_disponibili(p(X,Y),R):-
	XN is X+1,
	XS is X-1,
	YW is Y-1,
	YE is Y+1,
	getPath(Path),
	intersection(Path,[stato(p(XN,Y),_,_,_,_,_),stato(p(XS,Y),_,_,_,_,_),stato(p(X,YW),_,_,_,_,_),stato(p(X,YE),_,_,_,_,_)],L1),
	lunghezza_path(L1,[],N), 
	R is 4-N.

%LUNGHEZZA PATH
%lunghezza_path([stato(+P,_,_,_,_,_)|+T],+L,-N)
%					det: N è la lunghezza del Path [stato(+P,_,_,_,_,_)|+T]
%						 calcolata utilizzando L come lista ausiliaria ([])
%						 e senza considerare le ripetizioni delle celle 'rotazione'
lunghezza_path([],L,N):-
	length(L,N).
lunghezza_path([stato(P,_,_,_,_,_)|T],L,N):-
	member(stato(P,_,_,_,_,_),L)->lunghezza_path(T,L,N); %non considera le rotazioni
	(append([stato(P,_,_,_,_,_)],L,Temp),
	lunghezza_path(T,Temp,N)).
	
%--------------------------------------------
%--------------- PROBABILITA' ---------------
%--------------------------------------------
%PROBABILITÀ
%prob(+Pos,-Prob) det: Prob è la probabilità che dalla posizione Pos ci si possa
%					   muovere senza sopravvivere infatti:
%					   abs(Risk-1)^-1 più il Risk è alto più Prob è bassa
%					   dipende dalle direzioni disponibili (R), dal rischio (Risk)
%					   dalla lista delle percezioni (Plist) da cui si estrae se il
%					   rischio sarà di cadere in un pozzo, o in una cella con Wumpus
%					   (nel qual caso si dovrà sparare) 
prob(Pos,Prob):-
	make_perc(Pos,Plist),
	direzioni_disponibili(Pos,R),
	(nth0(1,Plist,1)-> %se sento brezza calcolo il rischio (probabilità)
		((R=:=1) -> Risk is 0.0000001 ; risk(Risk));
		(risk(Risk))),
		%condizione aggiunta per scongiuare Nan dovuti a divisioni
		%per 0
		((Risk=:=1)->Prob is 100000;Prob is 1/abs(Risk-1)).

%---------------------------------------
%-------------- EURISTICA --------------
%---------------------------------------	
%h(_,0).
h(stato(P,_D,_Plist,_Zaino,Tesori,_A), H) :-
	max_dist(P,Tesori,H).

%-------------------------------------------------
%-------------- PREDICATI AUSILIARI --------------
%-------------------------------------------------
max_dist(_,[],-1).
max_dist(P,[P1|L],H) :-
	distanza_griglia(P,P1,H1),
	max_dist(P,L,H2),
	max(H1,H2,H).

%MAX
%max(?A,?B,?C) semidet: C è il massimo tra A e B
%						A è C se C è maggiore di B
%						B è C se C è maggiore di A
max(X,Y,Y) :- X =< Y.
max(X,Y,X) :- X > Y.

%PRODOTTO SCALARE
%scal([DX1,DY1],[DX2,DY2],0) det: mi dice se due direzioni sono perpendicolari 
scal([0,1],[1,0],0).
scal([1,0],[0,1],0).
scal([0,-1],[-1,0],0).
scal([-1,0],[0,-1],0).
scal([0,1],[-1,0],0).
scal([1,0],[0,-1],0).
scal([0,-1],[1,0],0).
scal([-1,0],[0,1],0).

replace([_|T], 0, X, [X|T]).
replace([H|T], I, X, [H|R]):-
	I > 0, I1 is I-1,
	replace(T, I1, X, R).

count([],_,0).
count([X|T],X,Y):- count(T,X,Z), Y is 1+Z.
count([X1|T],X,Z):- X1\=X,count(T,X,Z).

countall(List,X,C) :-
    sort(List,List1),
    member(X,List1),
    count(List,X,C).
   
sum(p(X,Y),dir(DX,DY),p(X1,Y1)):-
	X1 is X+DX,
	Y1 is Y+DY,
	ok(p(X1,Y1)).